# home page: https://github.com/bcherny/format-as-currency
# 金额输入控件
## 用户输入限制
#### 只能输入 [数字 左右方向键 删除键 回退键]
#### keydown事件拦截输入

module.exports = angular.module 'libs.directives.currency', []
.directive 'currency', ($log, $q, $filter, $locale, CommonService)->
  commonService = new CommonService()

  # eg. 123.00, "$123.00" => ["$", ","]
  uniqueChars = (a, b)->
    return if typeof a isnt 'string'
    return if typeof b isnt 'string'
    chars = a.split ''
    uniq b.split('').sort().reduce(((prev, cur) ->
      if chars.indexOf(cur) < 0 then prev.concat(cur) else prev
    ), [])

  uniq = (array) ->
    array.reduce ((prev, cur) ->
      if prev.indexOf(cur) > -1 then prev else prev.concat(cur)
    ), []

  occurrences = (haystack, needles) ->
    return if typeof haystack isnt 'string'
    return if Object.prototype.toString.call(needles) isnt '[object Array]'

    needles.forEach (needle, n) ->
      return if typeof needle isnt 'string'

    return needles.map((needle) ->
      _needle = needle.replace(/\[/g, '\\[').replace(/\]/g, '\\]')
      (haystack.match(new RegExp('[' + _needle + ']', 'g')) or []).length
    ).reduce((prev, cur) ->
      prev + cur
    , 0)

  # validate
  min = (num, min, cb)->
    return if typeof num isnt 'number'
    return if typeof min isnt 'number'
    valid = num > 0
    cb(valid) if cb?
#    cb() if cb? and num < min

  # 拦截用户输入
  # 只允许 [数字 左右方向键 tab键 enter键 删除键 回退键] 输入
  # [删除键 会退键] 输入, 需要判断是否是可删除字符[. , currencySymbol], 非可删除字符不允许删除
  onKeydown = (elem, currencySymbol)->
    elem.on 'keydown', (event)->
      numberKeyCodes = [48,49, 50, 51, 52, 53, 54, 55, 56, 57, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105]
      deleteKeyCodes = [8, 46]
      allowKeyCodes = _.union [9, 13, 37, 39], numberKeyCodes
      forbiddenDeleteChars = ['.', ',', currencySymbol]

      return true if allowKeyCodes.indexOf(event.keyCode) isnt -1
      return false if deleteKeyCodes.indexOf(event.keyCode) is - 1
      selectedChars = @value.substring ( if @selectionEnd - @selectionStart is 0 then @selectionStart - 1 else @selectionStart ), @selectionEnd
      return false for forbiddenDeleteChar in forbiddenDeleteChars when selectedChars.indexOf(forbiddenDeleteChar) isnt -1

  {
    require: 'ngModel'
    restrict: 'A'
    link: (scope, elem, attrs, ngModel)->
      currencySymbol = attrs.currencySymbol || '¥'
      currencyFraction = attrs.currencyFraction || 2
      currencyMin = attrs.currencyMin || 0

      onKeydown elem, currencySymbol

      scope.$watch attrs.ngModel, (newVal, oldVal)->
        return if !newVal?
        min parseFloat(newVal), 0, (valid)->
          ngModel.$setValidity "minAmount", valid


      filter = $filter('currency')

      ngModel.$formatters.push (value) -> filter value, currencySymbol, currencyFraction

      ngModel.$parsers.push (value) ->
# 替换掉所有非数字字符, 只留[0-9]和.
        reg = eval "/[^0-9,.\\" + currencySymbol + "]/g"
        value = value.replace reg, ''
        if value is currencySymbol
          decimal = ''
          decimal += '0' for i in [0...currencyFraction]
          value = if decimal is '' then "#{currencySymbol}0" else "#{currencySymbol}0.#{decimal}"

        multiple = 1
        multiple *= 10 for i in [0...currencyFraction]
        number = (Math.floor(commonService.currencyToFloat(value, currencySymbol) * multiple) / multiple).toFixed(currencyFraction)

        return number if !ngModel.$validators.currency(number)

        min parseFloat(number), parseFloat(currencyMin), (valid)->
          ngModel.$setValidity "minAmount", valid

        formatted = filter number, currencySymbol, currencyFraction
        specialCharacters = uniqueChars number, formatted
        # did we add a comma or currency symbol?
        specialCharactersCountChange = [
          value
          formatted
        ].map((string) ->
          occurrences string, specialCharacters
        ).reduce((prev, cur) ->
          cur - prev
        )

        # compute the new selection range, correcting for
        # formatting introduced by the currency $filter
        selectonRange = [
          elem[0].selectionStart
          elem[0].selectionEnd
        ].map((position) ->
          position + specialCharactersCountChange
        )

        # set the formatted value in the view
        ngModel.$setViewValue formatted
        ngModel.$render()
        # set the cursor back to its expected position
        # (since $render resets the cursor the the end)
        elem[0].setSelectionRange selectonRange[0], selectonRange[1]

        return number

      ngModel.$validators.currency = (modelValue) -> !isNaN modelValue
  }